package com.class5.springboot.frame.repo.tool;

import com.class5.springboot.frame.repo.tool.xml.*;
import com.class5.springboot.util.StringUtils;
import com.github.javaparser.JavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.Modifier;
import com.github.javaparser.ast.body.*;
import com.github.javaparser.ast.comments.JavadocComment;
import com.github.javaparser.ast.expr.Expression;
import com.github.javaparser.ast.expr.IntegerLiteralExpr;
import com.github.javaparser.ast.expr.StringLiteralExpr;
import com.github.javaparser.ast.stmt.BlockStmt;
import com.github.javaparser.ast.type.ClassOrInterfaceType;
import com.github.javaparser.printer.PrettyPrinterConfiguration;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;

/**
 * 该类用于根据数据库表生成实体类代码及相关的视图类代码，业务类及Controller类
 *
 * @author Administrator
 *
 */
public class EntityClassGenerator {
    private Config config;

    public EntityClassGenerator(Config config) {
        this.config = config;
    }

    /**
     * 根据db.xml文件生成java类
     *
     * @param file
     * @throws Exception
     */
    public void generatorCode(String file,boolean coverFlg) throws Exception {
        DbProject project = DbProjectFactory.get().build(new File(file));
        generatorCode(project,coverFlg);

    }
    public void generatorCode(DbProject project,boolean coverFlg) throws Exception {
        //生成常量类
        for(DbCodeGroup group : project.getCodeGroup().values()){
            generatorCodeClass(group, project,coverFlg);
        }
        //生成实体类
        for (DbTable table : project.getTables()) {
            generatorAndUpdateEntityClassFile(table, project,coverFlg);
        }

    }
    private void generatorCodeClass(DbCodeGroup g, DbProject prj,boolean coverFlg)throws Exception{
        String className = g.getClassName();
        String packageNamme = prj.getEntityPackage();
        File javafile = new File(config.getEntitySrcPath(),
                packageNamme.replace(".", "/") + "/code/" + className + ".java");
        String signstr = g.getComment();
        ClassOrInterfaceDeclaration classCode = null;
        CompilationUnit fileCode = null;
        if (javafile.exists()) {
            if(coverFlg){
                javafile.delete();
            }else{
                fileCode = JavaParser.parse(javafile);
                classCode = fileCode.getClassByName(className).get();
                //未修改不更新
                if(classCode.getJavadocComment().isPresent()){
                    if(classCode.getJavadocComment().get().getContent().indexOf(signstr) > 0){
                        return;
                    }
                }
            }
        }
        /**
         *
         classCode.addImplementedType("com.teynet.frame.code.CodeItemUtils");
         classCode.addImplementedType("java.util.ArrayList");
         classCode.addImplementedType("java.util.List");
         */
        fileCode = new CompilationUnit(packageNamme+".code");
        fileCode.addImport("com.class5.springboot.frame.code.CodeItemUtils");
        fileCode.addImport("com.class5.springboot.frame.code.CodeItem");
        fileCode.addImport("java.util.ArrayList");
        fileCode.addImport("java.util.List");
        classCode = fileCode.addClass(className, Modifier.FINAL, Modifier.PUBLIC);
		/*if(config.getSuperEntity() != null)
			classCode.addExtendedType(config.getSuperEntity());*/
        classCode.setJavadocComment("\n * "
                + signstr
                + "\n *");
        Expression exp = null;
        String ftype = "String";
        for(DbCodeItem item : g.getItems()){
            if("int".equals(g.getType())){
                ftype = "Integer";
                exp = new IntegerLiteralExpr(Integer.parseInt(item.getValue()));
            }else{
                exp = new StringLiteralExpr(item.getValue());
            }
            FieldDeclaration fd = classCode.addField(ftype, item.getField(), Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL);
            fd.getVariables().iterator().next().setInitializer(exp);
            fd.setJavadocComment(item.getText() + (item.getNotes()!=null?","+item.getNotes():""));
        }
        //生成getVal,getText
        MethodDeclaration getValMethod = classCode.addMethod("getVal",Modifier.PUBLIC,Modifier.STATIC);
        getValMethod.addParameter(String.class,"text");
        getValMethod.setType(ftype);
        BlockStmt blockStmt = new BlockStmt();
        blockStmt.addStatement(ftype+" val = null;");
        for(DbCodeItem item : g.getItems()){
            blockStmt.addStatement("if(\""+item.getText()+"\".equals(text)){val = "+item.getField()+";}");
        }
        blockStmt.addStatement("return val;");
        getValMethod.setBody(blockStmt);

        MethodDeclaration getTextMethod = classCode.addMethod("getText",Modifier.PUBLIC,Modifier.STATIC);
        getTextMethod.addParameter(ftype,"val");
        getTextMethod.setType(String.class);
        blockStmt = new BlockStmt();
        blockStmt.addStatement("String text= null;");
        for(DbCodeItem item : g.getItems()){
            blockStmt.addStatement("if("+item.getField()+".equals(val)){text = \""+item.getText()+"\";}");
        }
        blockStmt.addStatement("return text;");
        getTextMethod.setBody(blockStmt);

        MethodDeclaration getListMethod = classCode.addMethod("getList",Modifier.PUBLIC,Modifier.STATIC);
        getListMethod.setType(List.class);
        blockStmt = new BlockStmt();
        blockStmt.addStatement("List<CodeItem> list = new ArrayList<>();");
        for(DbCodeItem item : g.getItems()){
            blockStmt.addStatement("list.add(CodeItemUtils.getCodeItem("+item.getField()+",getText("+item.getField()+")));");
        }
        blockStmt.addStatement("return list;");
        getListMethod.setBody(blockStmt);

        PrettyPrinterConfiguration pc = new PrettyPrinterConfiguration();
        String code = fileCode.toString(pc);
        FileUtils.write(javafile, code);
        System.out.println("Generator code class:" + packageNamme + "."
                + className);
    }
    /**
     * 获取源代码包装对象
     *
     * @return
     * @throws Exception
     */
    private void generatorAndUpdateEntityClassFile(DbTable table, DbProject prj,boolean coverFlg)
            throws Exception {
        // 获取类名
        String className = table.getEntity();
        if(table.isNoentity())
            return;
        String entityPackage = className.substring(0,
                className.lastIndexOf("."));
        className = className.substring(className.lastIndexOf(".") + 1);
        File javafile = new File(config.getEntitySrcPath(),
                entityPackage.replace(".", "/") + "/" + className + ".java");
        ClassOrInterfaceDeclaration classCode = null;
        CompilationUnit fileCode = null;
        if (javafile.exists()) {
            if(coverFlg){
                javafile.delete();
            }else{
                fileCode = JavaParser.parse(javafile);
                if(fileCode.getClassByName(className).isPresent())
                    classCode = fileCode.getClassByName(className).get();
            }
        }
        if(classCode == null){
            fileCode = new CompilationUnit(entityPackage);
            classCode = fileCode.addClass(className);
            if(config.getSuperEntity() != null)
                classCode.addExtendedType(config.getSuperEntity());
            classCode.addImplementedType(Serializable.class);
            classCode.addSingleMemberAnnotation(SuppressWarnings.class, "\"serial\"");
            classCode.setJavadocComment("\n * "
                    + table.getName()
                    + " "
                    + table.getComment()
                    + " "
                    + (StringUtils.isTrimEmpty(table.getNotes()) ? "" : table
                    .getNotes()) + "\n *");
        }
        boolean changed = false;
        // 删除未定义，且没有添加@Transent的属性
        List<FieldDeclaration> removes = new ArrayList<FieldDeclaration>();
        for (FieldDeclaration fd : classCode.getFields()) {
            if (fd.getJavadocComment().isPresent()
                    && fd.getJavadocComment().get().getContent()
                    .indexOf("@Transent") >= 0) {
                continue;
            }
            String fname = fd.getVariables().get(0).toString();
            if (table.getColumnByField(fname) == null) {
                // 需要删除
                removes.add(fd);
            }
        }
        for (FieldDeclaration fd : removes) {
            // 移除字段
            classCode.remove(fd);
            // 移除get与set
            String fname = fd.getVariables().get(0).toString();
            String geter = "get" + fname.substring(0, 1).toUpperCase()
                    + fname.substring(1);
            String seter = "set" + fname.substring(0, 1).toUpperCase()
                    + fname.substring(1);
            List<MethodDeclaration> ls = new ArrayList<>();
            ls.addAll(classCode.getMethodsByName(geter));
            ls.addAll(classCode.getMethodsByName(seter));
            for (MethodDeclaration md : ls) {
                classCode.remove(md);
            }
            changed = true;
        }
        // 先添加没有的字段
        for (DbTableColumn c : table.getColumns()) {
            if(c.isNofield())
                continue;
            if (classCode.getFieldByName(c.getField()).isPresent()) {
                continue;
            }
            FieldDeclaration fd = classCode.addField(c.getFieldType(),
                    c.getField(), Modifier.PRIVATE);
            // 添加注解
            String refgroup = c.getRefCodeGroup();
            if(refgroup != null){
                DbCodeGroup g = prj.getCodeGroup().get(refgroup);
                refgroup = g.getClassName() + "[" + g.getComment() + "]";
            }
            JavadocComment jc = new JavadocComment(c.getComment()
                    + (refgroup == null ?"":("(" + refgroup + ")"))
                    + (StringUtils.isTrimEmpty(c.getNotes()) ? ""
                    : (":" + c.getNotes())) + "(" + c.getName() + ")");
            fd.setJavadocComment(jc);
            changed = true;
        }
        if (changed) {
            // 生成get与set,toString,equals,hasCode
            generateGetSetToStringAndHashCode(classCode, table);
            // 调一下顺序，方法放在最后
            List<MethodDeclaration> mds = classCode.getMethods();
            for (MethodDeclaration m : mds) {
                classCode.remove(m);
            }
            if(classCode.getConstructors().isEmpty()){
                // 添加无参构造函数
                classCode.addConstructor(Modifier.PUBLIC);
                // 添加不能为空的参数的构造函数
                ConstructorDeclaration constructor = classCode
                        .addConstructor(Modifier.PUBLIC);
                for (DbTableColumn c : table.getColumns()) {
                    if (!c.isNullable()) {
                        constructor.addParameter(c.getFieldType(), c.getField());
                        constructor.getBody()
                                .addStatement(
                                        "this." + c.getField() + " = "
                                                + c.getField() + ";");
                    }
                }
            }
            for (MethodDeclaration m : mds) {
                classCode.addMember(m);
            }
            PrettyPrinterConfiguration pc = new PrettyPrinterConfiguration();
            String code = fileCode.toString(pc);
            FileUtils.write(javafile, code);
            System.out.println("Generator entity class:" + entityPackage + "."
                    + className);

        }
    }


    /**
     * 生成get，set,toString方法
     *
     * @param classCode
     */
    @SuppressWarnings("deprecation")
    public void generateGetSetToStringAndHashCode(
            ClassOrInterfaceDeclaration classCode, DbTable table) {
        StringBuffer strtoken = new StringBuffer("");
        for (FieldDeclaration fd : classCode.getFields()) {
            String fieldName = fd.getVariable(0).toString();
            // 用于toString
            strtoken.append("+\"," + fieldName + ":\" + " + fieldName);
            // 添加get方法
            String fieldNameUpper = fieldName.toUpperCase().substring(0, 1)
                    + fieldName.substring(1, fieldName.length());
            String getName = "get" + fieldNameUpper;
            String setName = "set" + fieldNameUpper;
            if (classCode.getMethodsByName(getName).isEmpty()) {
                fd.createGetter();// .setJavadocComment(fd.getComment().get().getContent());
            }
            // 添加带return this的set方法
            if (classCode.getMethodsByName(setName).isEmpty()) {
                MethodDeclaration set = fd.createSetter();
                set.setType(classCode.getNameAsString());
                // set.setJavadocComment("设置【" +
                // fd.getComment().get().getContent() + "】");
                BlockStmt stmt = new BlockStmt();
                if(!classCode.getExtendedTypes().isEmpty()){
                    String str = "addOriginalData(\"" + table.getColumnByField(fieldName).getName() + "\",this." + fieldName + "," + fieldName + ");";
                    stmt.addStatement(str);
                }
                stmt.addStatement("this." + fieldName + " = " + fieldName + ";");
                stmt.addStatement("return this;");
                set.setBody(stmt);
            }
        }
        // 重写toString
        if (!classCode.getMethodsByName("toString").isEmpty()) {
            classCode.remove(classCode.getMethodsByName("toString").get(0));
        }
        MethodDeclaration tostr = new MethodDeclaration(EnumSet.copyOf(Arrays
                .asList(Modifier.PUBLIC)), new ClassOrInterfaceType("String"),
                "toString");
        tostr.addAnnotation(Override.class);
        String str2 = "";
        if (!strtoken.toString().isEmpty()) {
            str2 = strtoken.toString().substring(1);
            str2 = str2.substring(0, str2.length());
            str2 = "this.getClass().getName() + \":{" + str2.substring(2)
                    + "+\"}\"";
        } else {
            str2 = "\"\"";
        }
        tostr.getBody().get().addStatement("return " + str2 + ";");
        classCode.addMember(tostr);
        // 生成equals
        if (classCode.getMethodsByName("equals").isEmpty()) {
            MethodDeclaration eq = new MethodDeclaration(EnumSet.copyOf(Arrays
                    .asList(Modifier.PUBLIC)), new ClassOrInterfaceType(
                    "boolean"), "equals");
            eq.addParameter("Object", "arg0");
            eq.addAnnotation(Override.class);
            str2 = "return ((" + classCode.getNameAsString()
                    + ")arg0).getId().equals(id);";
            eq.getBody().get().addStatement(str2);
            classCode.addMember(eq);
        }
        // 生成hasCode
        if (classCode.getMethodsByName("hashCode").isEmpty()) {
            MethodDeclaration hash = new MethodDeclaration(
                    EnumSet.copyOf(Arrays.asList(Modifier.PUBLIC)),
                    new ClassOrInterfaceType("int"), "hashCode");
            hash.addAnnotation(Override.class);
            str2 = "return id==null?0:id.hashCode();";
            hash.getBody().get().addStatement(str2);
            classCode.addMember(hash);
        }
    }
    public static void generate(String dbfilepath, Config config,boolean coverFlg) {
        try {
            new EntityClassGenerator(config).generatorCode(dbfilepath,coverFlg);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static void generate(String dbfilepath, Config config) {
        try {
            new EntityClassGenerator(config).generatorCode(dbfilepath,false);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static void generate(DbProject prj, Config config) {
        try {
            new EntityClassGenerator(config).generatorCode(prj,false);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}